package org.halk.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.halk.annotation.Lock;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * lock aop
 *
 * @Author halk
 * @Date 2024/9/18 16:58
 **/
@Aspect
@Slf4j
@Component
public class LockAspect {

    private final LockService lockService;

    public LockAspect(LockService lockService) {
        this.lockService = lockService;
    }

    @Around("@annotation(org.halk.annotation.Lock)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        log.info("[lock] - " + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());

        MethodSignature signature = (MethodSignature) point.getSignature();
        Lock lock = signature.getMethod().getAnnotation(Lock.class);
        String key = lock.key();
        long expire = lock.expireTime();
        long waitTime = lock.waitTime();
        Lock.KeyType keyType = lock.keyType();
        if (waitTime <= 0) {
            throw new RuntimeException("waitTime必须大于0");
        }
        if (expire <= 0) {
            throw new RuntimeException("@Lock注解使用错误！expire必须大于0");
        }
        
        switch (keyType) {
            case CONSTANT:
                if (!StringUtils.hasText(key)) {
                    throw new RuntimeException("@Lock注解使用错误！锁的key指定时，key不可以为空");
                }
                break;
            case PARAMETER:
                String methodName = lock.methodName();
                if (!StringUtils.hasText(methodName)) {
                    throw new RuntimeException("@Lock注解使用错误！锁的key使用参数时，methodName不能为空");
                }
                Object[] args = point.getArgs();
                if (args.length == 0) {
                    throw new RuntimeException("@Lock注解使用错误！锁的key使用参数时，方法必须有一个参数");
                }

                int paramOrder = lock.paramOrder();
                if (paramOrder < 0 || paramOrder + 1 > args.length) {
                    throw new RuntimeException("@Lock注解使用错误！paramOrder的参数范围为0-" + args.length + ", 当前值为：" + paramOrder);
                }

                Object arg = args[paramOrder];
                if (isPrimitiveType(arg)) {
                    key = String.valueOf(arg);
                } else {
                    Object invoke = null;
                    try {
                        Method declaredMethod = arg.getClass().getMethod(methodName);
                        invoke = declaredMethod.invoke(arg);
                    }catch (Exception e) {
                        throw new RuntimeException("@Lock注解使用错误！methodName没有对应的属性", e);
                    }
                    key = String.valueOf(invoke);
                }
                break;
            default:
                throw new RuntimeException("@Lock注解使用错误！keyType不能为空");
        }

        return tryLock(point, lock.keyPrefix(), key,   expire, waitTime);
    }

    public boolean isPrimitiveType(Object obj) {
        if (obj == null) {
            return false;
        }
        Class<?> clazz = obj.getClass();
        return clazz.equals(Integer.class) ||
                clazz.equals(Byte.class) ||
                clazz.equals(Short.class) ||
                clazz.equals(Long.class) ||
                clazz.equals(Float.class) ||
                clazz.equals(Double.class) ||
                clazz.equals(Character.class) ||
                clazz.equals(String.class) ||
                clazz.equals(Boolean.class);
    }

    private Object tryLock(ProceedingJoinPoint point, String keyPrefix, String key, long expire, long waitTime) throws Throwable {
        Boolean b = this.lockService.lock(keyPrefix + key, expire);
        Object result;
        if (b != null && b.equals(true)) {
            try {
                result = point.proceed();
            } finally {
                this.lockService.unLock(keyPrefix + key);
            }
            return result;
        } else {
            //等待几秒，尝试循环获取
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= waitTime) {
                TimeUnit.MILLISECONDS.sleep(200);
                Boolean b1 = this.lockService.lock(keyPrefix + key,  expire);
                if (b1 != null && b1.equals(true)) {
                    try {
                        result = point.proceed();
                    } finally {
                        this.lockService.unLock(keyPrefix + key);
                    }
                    return result;
                }
            }
            throw new Exception("资源被占用，获取锁失败");
        }
    }
}
